#include <fhe/core/Data.h>
#include <fhe/core/Util.h>
#include <sstream>

namespace fhe
{
  namespace core
  {
    Data::Data()
      : type_( UNKNOWN )
      , parent_( 0 )
    {
    }
    
    Data::~Data()
    {
    }
    
    std::string Data::path() const
    {
      return parent_ ? parent_->path() + "/" + getName() : getName();
    }
    
    Data::Type Data::getType() const
    {
      return type_;
    }
    
    std::string Data::getName() const
    {
      return name_;
    }
    
    void Data::setName( const std::string& name )
    {
      name_ = name;
    }
    
    bool Data::getBool() const
    {
      FHE_ASSERT_MSG( type_ == BOOL,
                      "unable to get bool from %s", 
                      path().c_str() );
      return b_;
    }
    
    void Data::setBool( bool b )
    {
      FHE_ASSERT_MSG( type_ == UNKNOWN || type_ == BOOL,
                      "unable to set bool to %s", 
                      path().c_str() );
      type_ = BOOL;
      b_ = b;
    }
    
    int Data::getInt() const
    {
      FHE_ASSERT_MSG( type_ == INT,
                      "unable to get int from %s", 
                      path().c_str() );
      return i_;
    }
    
    void Data::setInt( int i )
    {
      FHE_ASSERT_MSG( type_ == UNKNOWN || type_ == INT,
                      "unable to set int to %s", 
                      path().c_str() );
      type_ = INT;
      i_ = i;
    }
    
    double Data::getDouble() const
    {
      FHE_ASSERT_MSG( type_ == DOUBLE || type_ == INT,
                      "unable to get double from %s", 
                      path().c_str() );
      if ( type_ == DOUBLE )
      {
        return d_;
      }
      else
      {
        return i_;
      }
    }
    
    void Data::setDouble( double d )
    {
      FHE_ASSERT_MSG( type_ == UNKNOWN || type_ == DOUBLE || type_ == INT,
                      "unable to set double to %s", 
                      path().c_str() );
      type_ = DOUBLE;
      d_ = d;
    }
    
    std::string Data::getString() const
    {
      FHE_ASSERT_MSG( type_ == STRING,
                      "unable to get string from %s", 
                      path().c_str() );
      return s_;
    }
    
    void Data::setString( const std::string& s )
    {
      FHE_ASSERT_MSG( type_ == UNKNOWN || type_ == STRING,
                      "unable to set string to %s", 
                      path().c_str() );
      type_ = STRING;
      s_ = s;
    }
    
    bool Data::hasChild( const std::string& name ) const
    {
      FHE_ASSERT_MSG( type_ == UNKNOWN || type_ == PARENT,
                      "unable to check for child in %s", 
                      path().c_str() );
      for ( ChildIterator child = children_.begin(); 
            child != children_.end();
            ++child )
      {
        if ( (*child)->getName() == name )
        {
          return true;
        }
      }
      return false;
    }

    Data::Ptr Data::getChild( const std::string& name ) const
    {
      FHE_ASSERT_MSG( type_ == PARENT,
                      "unable to get child from %s", 
                      path().c_str() );
      for ( ChildIterator child = children_.begin(); 
            child != children_.end();
            ++child )
      {
        if ( (*child)->getName() == name )
        {
          return *child;
        }
      }
      FHE_ERROR( "unknown child %s in %s", name.c_str(), path().c_str() );
    }
    
    void Data::addChild( const Ptr& child )
    {
      FHE_ASSERT_MSG( type_ == UNKNOWN || type_ == PARENT,
                      "unable to add child to %s", 
                      path().c_str() );
      type_ = PARENT;
      children_.push_back( child );
      child->parent_ = this;
    }
    
    Data::Ptr Data::addChild( const std::string& name )
    {
      Ptr child( new Data );
      child->setName( name );
      addChild( child );
      return child;
    }
    
    Data::ChildIterator Data::childrenBegin() const
    {
      FHE_ASSERT_MSG( type_ == PARENT,
                      "unable to get children begin from %s", 
                      path().c_str() );
      return children_.begin();
    }

    Data::ChildIterator Data::childrenEnd() const
    {
      FHE_ASSERT_MSG( type_ == PARENT,
                      "unable to get children end from %s", 
                      path().c_str() );
      return children_.end();
    }
    
    void Data::setValueString( const std::string& s ) 
    {
      if ( s == "true" )
      {
        setBool( true );
      }
      else if ( s == "false" )
      {
        setBool( false );
      }
      else
      {
        std::istringstream is( s );
        double d;
        if ( s.find( '.' ) != std::string::npos && is >> d )
        {
          setDouble( d );
        }
        else
        {
          std::istringstream is( s );
          int i;
          if ( is >> i )
          {
            setInt( i );
          }
          else
          {
            setString( s );
          }
        }
      }
    }
    
    std::vector< Data::Ptr > Data::children() const
    {
      return children_;
    }
    
    Data::Ptr Data::get( const std::string& path, bool create ) const
    {
      std::vector< std::string > toks = StringUtil::split( path, "/" );
      FHE_ASSERT_MSG( !toks.empty(), "unable to get null path %s", path.c_str() );
      if ( !hasChild( toks[0] ) )
      {
        if ( create )
        {
          Ptr child( new Data );
          child->setName( toks[0] );
          const_cast<Data*>(this)->addChild( child );
        }
        else
        {
          FHE_ERROR( "unknown child %s", toks[0].c_str() );
        }
      }
      Ptr data = getChild( toks[0] );
      if ( toks.size() == 1 )
      {
        return data;
      }
      else
      {
        return data->get( path.substr( toks[0].size() + 1 ), create );
      }
    }
    
    template <>
    bool Data::get<bool>() const
    {
      return getBool();
    }
    
    template <>
    int Data::get<int>() const
    {
      return getInt();
    }
    
    template <>
    double Data::get<double>() const
    {
      return getDouble();
    }
    
    template <>
    std::string Data::get<std::string>() const
    {
      return getString();
    }
    
    template <>
    void Data::set<bool>( bool b ) 
    {
      setBool( b );
    }
    
    template <>
    void Data::set<int>( int i )
    {
      setInt( i );
    }
    
    template <>
    void Data::set<double>( double d )
    {
      setDouble( d );
    }
    
    template <>
    void Data::set<std::string>( std::string s )
    {
      setString( s );
    }
    
    template <>
    bool Data::is<bool>() const
    {
      return type_ == BOOL;
    }

    template <>
    bool Data::is<int>() const
    {
      return type_ == INT;
    }

    template <>
    bool Data::is<double>() const
    {
      return type_ == DOUBLE || type_ == INT;
    }

    template <>
    bool Data::is<std::string>() const
    {
      return type_ == STRING;
    }
    
    std::string Data::getValueString() const
    {
      std::ostringstream os;
      os << "Data( " << getName() << ", " << getTypeString();
      switch ( type_ )
      {
        case UNKNOWN:
        {
          break;
        }
        case PARENT:
        {
          for ( ChildIterator child = children_.begin(); child != children_.end(); ++child )
          {
            os << ", " << (*child)->getValueString();
          }
          break;
        }
        case BOOL:
        {
          os << ", " << getBool() ? "true" : "false";
          break;
        }
        case INT:
        {
          os << ", " << getInt();
          break;
        }
        case DOUBLE:
        {
          os << ", " << getDouble();
          break;
        }
        case STRING:
        {
          os << ", " << getString();
          break;
        }
      }
      os << " )";
      return os.str();
    }
    
    std::string Data::getTypeString() const
    {
      switch ( type_ )
      {
        case PARENT:
          return "PARENT";
        case BOOL:
          return "BOOL";
        case INT:
          return "INT";
        case DOUBLE:
          return "DOUBLE";
        case STRING:
          return "STRING";
        case UNKNOWN:
        default:
          return "UNKNOWN";
      }
    }
  }
}
